const numReg = /^(?<i>-?\d+)(?<f>\.\d+)?$/
const hmsReg = /^(?<h>\d+):(?<m>\d+):(?<s>\d+(?<f>\.\d+)?)$/
const dmsReg = /^(?<d>(-|\+)?\d+):(?<m>\d+):(?<s>\d+(?<f>\.\d+)?)$/

/**
 * 对给定的degree格式的ra进行校验
 * @param rad degree格式的ra
 * @returns {boolean} true/false
 */
const checkRaDeg = (rad) => {
    const ss = String(rad)
    const reRes = numReg.exec(ss)
    if (reRes == null) {
        console.warn('not matched: ra, deg, ' + rad)
        return false
    }
    // console.log(reRes)
    const i = parseInt(reRes.groups.i)
    const f = reRes.groups.f ? parseFloat(reRes.groups.f) : 0
    const v = i + f
    if (v < 0 || v >= 360) {
        console.warn('out of range: ra, deg, ' + v)
        return false
    }
    return true
}

/**
 * 对给定的degree格式的dec进行校验
 * @param decd degree格式的dec
 * @returns {boolean} true/false
 */
const checkDecDeg = (decd) => {
    const ss = String(decd)
    const reRes = numReg.exec(ss)
    if (reRes == null) {
        console.warn('not matched: dec, deg, ' + decd)
        return false
    }
    // console.log(reRes)
    const i = parseInt(reRes.groups.i)
    const f = reRes.groups.f ? parseFloat(reRes.groups.f) : 0
    const v = i + f
    if (v < -90 || v > 90) {
        console.warn('out of range: dec, deg, ' + v)
        return false
    }
    return true
}

/**
 * 对给定的hms格式的ra进行校验
 * @param hms dms格式的ra
 * @returns {boolean} true/false
 */
const checkRaHms = (hms) => {
    const ss = String(hms)
    const reRes = hmsReg.exec(ss)
    if (reRes == null) {
        console.warn('not matched: ra, hms, ' + hms)
        return false
    }
    // console.log(reRes)
    const h = parseInt(reRes.groups.h)
    const m = parseInt(reRes.groups.m)
    const s = parseFloat(reRes.groups.s)
    if (s < 0 || s >= 60) {
        console.warn('out of range: ra, s, ' + s)
        return false
    }
    if (m < 0 || m >= 60) {
        console.warn('out of range: ra, m, ' + m)
        return false
    }
    if (h < 0 || h >= 24) {
        console.warn('out of range: ra, h, ' + h)
        return false
    }
    return true
}

/**
 * 对给定的dms格式的dec进行校验
 * @param dms dms格式的dec
 * @returns {boolean} true/false
 */
const checkDecDms = (dms) => {
    const ss = String(dms)
    const reRes = dmsReg.exec(ss)
    if (reRes == null) {
        console.warn('not matched: dec, dms, ' + dms)
        return false
    }
    // console.log(reRes)
    const d = parseInt(reRes.groups.d)
    const m = parseInt(reRes.groups.m)
    const s = parseFloat(reRes.groups.s)
    // console.log(d, m, s)
    if (s < 0 || s >= 60) {
        console.warn('out of range: dec, s, ' + s)
        return false
    }
    if (m < 0 || m >= 60) {
        console.warn('out of range: dec, m, ' + m)
        return false
    }
    if (d < -90 || d > 90) {
        console.warn('out of range: dec, d, ' + d)
        return false
    }
    // if ((d === -90 || d === 90) && (m !== 0 || s > Number.EPSILON)) {
    //   console.warn('out of range: dec, dms, ' + dms)
    //   return false
    // }
    if ((d === -90 || d === 90) && (m !== 0 || s)) {
        console.warn('out of range: dec, dms, ' + dms)
        return false
    }
    return true
}

/**
 * 对给定的字符串左位补0
 * @param l 期望的字符串总长度
 * @param str 给定的字符串
 * @returns {string} 返回期望长度的新字符串
 */
const foo = (l, str) => {
    str = '' + str
    if (l <= 0) {
        return str
    } else {
        // let s = ''
        // for (let i = 0; i < l; i++) {
        //   s += '0'
        // }
        // str = s + str
        // return str.substring(str.length - l, str.length)
        if (str.length >= l) {
            return str
        }
        return '0'.repeat(l - str.length) + str
    }
}

function foo2(str) {
    return foo(2, str)
}

function foo3(str) {
    return foo(3, str)
}

function foo4(str) {
    return foo(4, str)
}

function foo5(str) {
    return foo(5, str)
}

function foo6(str) {
    return foo(6, str)
}

/**
 * 将 degree 格式的 ra 转为 hms 格式
 * @param rad
 *    degree 格式的 ra. 如：120.5234
 * @returns {string|null}
 *    正常会返回 string 类型的 hms；当传入的参数没通过格式校验，会返回 null. 如: 08:02:05.616
 */
const degToHms = (rad) => {
    if (!checkRaDeg(rad)) {
        // throw new Error('Invalid ra: ' + rad)
        return null
    }
    try {
        const raVal = parseFloat(String(rad))
        const a = raVal / 15
        const b = (raVal - Math.floor(a) * 15) * 4
        const c = (b - Math.floor(b)) * 60
        return '' + foo2(Math.floor(a)) + ':' + foo2(Math.floor(b)) + ':' + foo6(c.toFixed(3))
    } catch (e) {
        console.warn(e)
        return null
    }
}

/**
 * 将 degree 格式的 dec 转为 dms 格式
 * @param decd
 *    degree 格式的 dec. 如: 30.1134
 * @returns {string|null}
 *    正常会返回 string 类型的 dms；当传入的参数没通过格式校验，会返回 null. 如: +30:06:48.24
 */
const degToDms = (decd) => {
    if (!checkDecDeg(decd)) {
        // throw new Error('Invalid dec: ' + decd)
        return null
    }
    try {
        let decVal = parseFloat(String(decd))
        let sign = '+'
        if (decVal < 0) {
            sign = '-'
            decVal = Math.abs(decVal)
        }
        const a = decVal
        const b = (a - Math.floor(a)) * 60
        const c = (b - Math.floor(b)) * 60
        return sign + foo2(Math.floor(a)) + ':' + foo2(Math.floor(b)) + ':' + foo5(c.toFixed(2))
    } catch (e) {
        console.warn(e)
        return null
    }
}

/**
 * 将 hms 格式的 ra 转为 degree
 * @param raStr
 *    hms 格式的 ra. 如：08:02:05.616
 * @returns {number|null}
 *    正常会返回 number 类型的 degree；当传入的参数没通过格式校验，会返回 null. 如: 120.5234
 */
const hmsToDeg = (raStr) => {
    if (!checkRaHms(raStr)) {
        // throw new Error('Invalid ra: ' + raStr)
        return null
    }
    try {
        const ras = String(raStr).split(':')
        let rad = null
        let ra = 0
        const raH = parseInt(ras[0])
        const raM = parseInt(ras[1])
        const raS = parseFloat(ras[2])
        ra += raH
        ra += raM / 60
        ra += raS / 3600
        ra = ra * 15
        rad = ra.toFixed(5)
        return parseFloat(rad)
    } catch (e) {
        console.warn(e)
        return null
    }
}

/**
 * 将 dms 格式的 dec 转为 degree 格式
 * @param decStr
 *    dms 格式的 dec. 如：+30:06:48.24
 * @returns {number|null}
 *    正常会返回 number 类型的 degree；当传入的参数没通过格式校验，会返回 null. 如: 30.1134
 */
const dmsToDeg = (decStr) => {
    if (!checkDecDms(decStr)) {
        // throw new Error('Invalid dec: ' + decStr)
        return null
    }
    try {
        const decs = String(decStr).split(':')
        let decd = null
        let dec = 0
        let flag = false
        let decD = parseInt(decs[0])
        const decM = parseInt(decs[1])
        const decS = parseFloat(decs[2])
        if (decD < 0) {
            flag = true
            decD = Math.abs(decD)
        }
        dec += decD
        dec += decM / 60
        dec += decS / 3600
        if (flag) {
            decd = (-dec).toFixed(5)
        } else {
            decd = dec.toFixed(5)
        }
        return parseFloat(decd)
    } catch (e) {
        console.warn(e)
        return null
    }
}


const formatRaDeg = (deg) => {
    if (!checkRaDeg(deg)) {
        return null
    }
    let d = parseFloat(String(deg))
    d = d.toFixed(4)
    d = parseFloat(d)
    return d
}

const formatDecDeg = (deg) => {
    if (!checkDecDeg(deg)) {
        return null
    }
    let d = parseFloat(String(deg))
    d = d.toFixed(3)
    d = parseFloat(d)
    return d
}

const formatRaHms = (hms) => {
    if (!checkRaHms(hms)) {
        return null
    }
    let d = String(hms)
    const ds = d.split(':')
    d = foo2(ds[0]) + ':' + foo2(ds[1]) + ':' + foo4(parseFloat(ds[2]).toFixed(1))
    return d
}

const formatDecDms = (dms) => {
    if (!checkDecDms(dms)) {
        return null
    }
    let d = String(dms)
    const sign = d.startsWith('-') ? '-' : '+'
    const ds = d.split(':')
    d = sign + foo2(Math.abs(parseInt(ds[0]))) + ':' + foo2(ds[1]) + ':' + foo2(parseFloat(ds[2]).toFixed(0))
    return d
}

/**
 * 强制将传入的 ra 转为 hms 格式
 * @param ra
 *    不限格式，可以是 degree, 也可以是 hms
 * @returns {string|null}
 *    正常会返回 string 类型的结果；当传入的参数没通过格式校验，会返回 null
 */
const raToHms = (ra) => {
    ra = ra + ''
    if (ra.indexOf(':') === -1) { // 不包含, 以数字形式处理
        return formatRaHms(degToHms(ra))
    } else { // 以hms形式处理
        return formatRaHms(ra)
    }
}

/**
 * 强制将传入的 ra 转为 degree 格式
 * @param ra
 *    不限格式，可以是 hms, 也可以是 degree
 * @returns {number|null}
 *    正常会返回 number 类型的结果；当传入的参数没通过格式校验，会返回 null
 */
const raToDeg = (ra) => {
    ra = ra + ''
    if (ra.indexOf(':') === -1) { // 不包含, 以数字形式处理
        return formatRaDeg(ra);
    } else { // 以hms形式处理
        return formatRaDeg(hmsToDeg(ra))
    }
}

/**
 * 强制将传入的 dec 转为 dms 格式
 * @param dec
 *    不限格式，可以是 degree, 也可以是 dms
 * @returns {string|null}
 *    正常会返回 string 类型的结果；当传入的参数没通过格式校验，会返回 null
 */
const decToDms = (dec) => {
    dec = dec + ''
    if (dec.indexOf(':') === -1) { // 不包含, 以数字形式处理
        return formatDecDms(degToDms(dec))
    } else { // 以dms形式处理
        return formatDecDms(dec)
    }
}

/**
 * 强制将传入的 dec 转为 degree 格式
 * @param dec
 *    不限格式，可以是 dms, 也可以是 degree
 * @returns {number|null}
 *    正常会返回 number 类型的结果；当传入的参数没通过格式校验，会返回 null
 */
const decToDeg = (dec) => {
    dec = dec + ''
    if (dec.indexOf(':') === -1) { // 不包含, 以数字形式处理
        return formatDecDeg(dec)
    } else { // 以dms形式处理
        return formatDecDeg(dmsToDeg(dec))
    }
}

/**
 * 数值格式检验
 * @param num
 * @returns {boolean}
 */
const checkNum = (num) => {
    const ss = String(num)
    const res = numReg.exec(ss)
    return res != null;
}

/**
 * 将传入的 deg 数值格式化
 * @param deg
 *    不限格式，可以是 dms, 也可以是 degree
 * @returns {string|null}
 *    正常会返回 number 类型的结果；当传入的参数没通过格式校验，会返回 null
 */
const formatInDeg = (deg) => {
    if (!checkNum(deg)) {
        console.warn('invalid input, ' + deg)
        return null
    }
    return parseFloat(String(deg)).toFixed(4)
}

/**
 * 将传入的 deg 数值格式化 (转化为分显示)
 * @param deg
 *    不限格式，可以是 dms, 也可以是 degree
 * @returns {string|null}
 *    正常会返回 number 类型的结果；当传入的参数没通过格式校验，会返回 null
 */
const formatInMin = (deg) => {
    if (!checkNum(deg)) {
        console.warn('invalid input, ' + deg)
        return null
    }
    return (parseFloat(String(deg)) * 60).toFixed(4)
}

/**
 * 将传入的 deg 数值格式化 (转化为秒显示)
 * @param deg
 *    不限格式，可以是 dms, 也可以是 degree
 * @returns {string|null}
 *    正常会返回 number 类型的结果；当传入的参数没通过格式校验，会返回 null
 */
const formatInSec = (deg) => {
    if (!checkNum(deg)) {
        console.warn('invalid input, ' + deg)
        return null
    }
    return (parseFloat(String(deg)) * 60 * 60).toFixed(4)
}

export {
    checkRaDeg, // 对给定的degree格式的ra进行校验
    checkDecDeg, // 对给定的degree格式的dec进行校验
    checkRaHms, // 对给定的hms格式的ra进行校验
    checkDecDms, // 对给定的dms格式的dec进行校验
    // =============================================
    foo, // 对给定的字符串左位补0
    // =============================================
    degToHms, // 将 degree 格式的 ra 转为 hms 格式
    degToDms, // 将 degree 格式的 dec 转为 dms 格式
    hmsToDeg, // 将 hms 格式的 ra 转为 degree
    dmsToDeg, // 将 dms 格式的 dec 转为 degree 格式
    // =============================================
    raToHms, // 将传入的不定格式的 ra 转为 hms 格式
    raToDeg, // 将传入的不定格式的 ra 转为 degree 格式
    decToDms, // 将传入的不定格式的 dec 转为 dms 格式
    decToDeg, // 将传入的不定格式的 dec 转为 degree 格式
    // =============================================
    formatInDeg, // 将传入的 deg 数值格式化
    formatInMin, // 将传入的 deg 数值格式化 (转化为分显示)
    formatInSec, // 将传入的 deg 数值格式化 (转化为秒显示)
}
